package net

import (
	"myPaxos/manager"
	"net/http"
	"encoding/json"
	"myPaxos/logger"
	"bytes"
	"gopkg.in/oleiade/lane.v1"
	"github.com/op/go-logging"
	"time"
)

type Client struct {
	hostname       string  // self hostname
	serverInfo     string
	serverHostname string  // server hostname
	serverID       int
	logger         *logging.Logger
	httpClient     *http.Client
	failedQueue    *lane.Deque
	failedDump     *lane.Deque
	eventhub       *manager.Eventhub
	respHandler func(response *http.Response) (bool, error)
}

func NewClient(hostname, serverInfo,serverHostname string, eh *manager.Eventhub) *Client {
	c :=  &Client{
		hostname:       hostname,
		serverInfo:     serverInfo,
		logger:         logger.GetLogger("net"),
		httpClient:     &http.Client{},
		failedQueue:    lane.NewDeque(),
		failedDump:     lane.NewDeque(),
		eventhub:       eh,
		serverHostname: serverHostname,
	}
	c.respHandler = func(response *http.Response) (bool, error) {
		if response != nil {
			respContent := make([]byte, 0)
			response.Body.Read(respContent)
			pong := &manager.Pong{}
			err := json.Unmarshal(respContent, pong)
			if err != nil {
				return true, err
			}
			if c.serverHostname != pong.Hostname {
				c.logger.Warning("received hostname in Pong Event is not same as config, overwrite!!!")
			}
			c.serverID = pong.ID
			c.logger.Debugf("build connection to %s success!", c.serverHostname)
			return true, nil
		} else {
			return false, nil
		}
	}
	return c
}

const ContentType = "application/json;charset=utf-8"

// return type comment:
// bool: whether the event is sent out:
//   - FALSE means not PONG message , the error should be handled in this function
//   - TRUE means PONG message, the error should be handled in outer function
// error: whether there are errors in handling
func (c *Client) Write(w interface{}) (bool, error) {
	jbytes,err := json.Marshal(w)
	if err != nil {
		c.logger.Errorf("marshal input error: %s", err.Error())
		return false, err
	} else {
		resp,err := c.httpClient.Post("http://"+c.serverInfo+"/", ContentType, bytes.NewBuffer(jbytes))
		if err != nil {
			c.logger.Warningf("write message to remote server: %s error: %s", c.serverInfo, err.Error())
			c.failedQueue.Append(w)
			return false, err
		} else {
			return c.respHandler(resp)
		}
	}
}

func (c *Client) resend() {
	timer := time.NewTicker(4*time.Second)
	for {
		select {
		case <-timer.C:
			c.dumpFailed()
			for ;!c.failedDump.Empty(); {
				item := c.failedDump.Pop()
				c.Write(item)
			}
		}
	}
}

func (c *Client) dumpFailed(){
	for ;!c.failedQueue.Empty(); {
		c.failedDump.Append(c.failedQueue.Pop())
	}
}

// return value: bool => whether sent out
//               error: error during sending
func (c *Client) connect() (bool, error){
	// send Ping message and handle Pong response
	c.logger.Debugf("write Ping to server")
	payload,err := json.Marshal(&manager.Ping{Hostname:c.hostname})
	if err != nil {
		c.logger.Errorf("marshal Ping message error: %s", err.Error())
		return false, err
	}
	wrapper := &manager.Wrapper{
		Hostname: c.hostname,
		Payload:  payload,
		Type:     manager.Ping_Wrapper,
	}
	return c.Write(wrapper)
}
